// File: jmcomic-core/src/main/java/dev/jukomu/jmcomic/exception/ExceptionUtils.java
package dev.jukomu.jmcomic.exception;

import dev.jukomu.common.util.net.IResponse;
import dev.jukomu.jmcomic.config.JmModuleConfig;
import dev.jukomu.jmcomic.util.JmcomicText;
import okhttp3.CookieJar;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
 * 对应Python的ExceptionTool，提供抛出自定义异常的工具方法。
 */
public final class ExceptionUtils {

    public static final String CONTEXT_KEY_RESP = "resp";
    public static final String CONTEXT_KEY_HTML = "html";
    public static final String CONTEXT_KEY_RE_PATTERN = "pattern";
    public static final String CONTEXT_KEY_MISSING_JM_ID = "missing_jm_id";
    public static final String CONTEXT_KEY_DOWNLOADER = "downloader";
    public static final String CONTEXT_KEY_CHALLENGE_URL = "challengeUrl";
    public static final String CONTEXT_KEY_COOKIE_JAR = "cookieJar";


    /**
     * 抛出自定义异常。
     *
     * @param message       异常消息
     * @param context       异常上下文Map
     * @param exceptionType 异常类型
     */
    public static void raises(String message, Map<String, Object> context, Class<? extends JmcomicException> exceptionType) {
        JmcomicException exceptionInstance = null;
        try {
            // 使用反射创建异常实例
            exceptionInstance = exceptionType
                    .getConstructor(String.class, Map.class)
                    .newInstance(message, context != null ? context : new HashMap<>()); // 确保context不为null
        } catch (Exception e) {
            // 如果创建失败，则抛出JmcomicException作为回退
            throw new JmcomicException("Failed to create specific exception: " + exceptionType.getName(), context != null ? context : new HashMap<>(), e);
        }

        // 通知所有监听器
        notifyAllListeners(exceptionInstance);

        throw exceptionInstance;
    }

    /**
     * 抛出默认的JmcomicException。
     *
     * @param message 异常消息
     * @param context 异常上下文Map
     */
    public static void raises(String message, Map<String, Object> context) {
        raises(message, context, JmcomicException.class);
    }

    /**
     * 抛出默认的JmcomicException，不带上下文。
     *
     * @param message 异常消息
     */
    public static void raises(String message) {
        raises(message, null, JmcomicException.class);
    }

    /**
     * 抛出正则表达式不匹配异常。
     *
     * @param message 异常消息
     * @param html    HTML内容
     * @param pattern 正则表达式模式
     */
    public static void raisesRegex(String message, String html, Pattern pattern) {
        Map<String, Object> context = new HashMap<>();
        context.put(CONTEXT_KEY_HTML, html);
        context.put(CONTEXT_KEY_RE_PATTERN, pattern);
        raises(message, context, RegularNotMatchException.class);
    }

    /**
     * 抛出响应不符合预期异常。
     *
     * @param message       异常消息
     * @param resp          响应对象
     * @param exceptionType 异常类型
     */
    public static void raisesResp(String message, IResponse resp, Class<? extends ResponseUnexpectedException> exceptionType) {
        Objects.requireNonNull(resp, "Response object cannot be null when raising a response-related exception.");
        Map<String, Object> context = new HashMap<>();
        context.put(CONTEXT_KEY_RESP, resp);
        raises(message, context, exceptionType);
    }

    /**
     * 抛出响应不符合预期异常，使用默认异常类型。
     *
     * @param message 异常消息
     * @param resp    响应对象
     */
    public static void raisesResp(String message, IResponse resp) {
        raisesResp(message, resp, ResponseUnexpectedException.class);
    }

    /**
     * 抛出本子/章节不存在的异常。
     *
     * @param resp 响应对象
     * @param jmId 缺失的禁漫ID
     */
    public static void raiseMissing(IResponse resp, String jmId) {
        String url = JmcomicText.formatAlbumUrl(jmId); // 直接使用jmId格式化URL
        String reqType = url.contains("album") ? "本子" : "章节";
        String message = String.format(
                "请求的%s不存在！(%s)\n" +
                        "原因可能为:\n" +
                        "1. id有误，检查你的%s id\n" +
                        "2. 该漫画只对登录用户可见，请配置你的cookies，或者使用移动端Client（api）",
                reqType, url, reqType
        );
        Map<String, Object> context = new HashMap<>();
        context.put(CONTEXT_KEY_RESP, resp);
        context.put(CONTEXT_KEY_MISSING_JM_ID, jmId);
        raises(message, context, MissingAlbumPhotoException.class);
    }

    /**
     * 抛出Cloudflare挑战异常。
     *
     * @param message           异常消息
     * @param challengeResponse 包含挑战页面的响应对象
     * @param currentCookieJar  当前的CookieJar实例
     */
    public static void raiseCloudflareChallenge(String message, IResponse challengeResponse, CookieJar currentCookieJar) {
        Objects.requireNonNull(challengeResponse, "Challenge response cannot be null.");
        Objects.requireNonNull(currentCookieJar, "Current CookieJar cannot be null.");

        String challengeUrl = challengeResponse.getUrl();
        Map<String, Object> context = new HashMap<>();
        context.put(CONTEXT_KEY_RESP, challengeResponse);
        context.put(CONTEXT_KEY_CHALLENGE_URL, challengeUrl);
        context.put(CONTEXT_KEY_COOKIE_JAR, currentCookieJar);

        // 使用反射创建CloudflareChallengeException实例
        try {
            CloudflareChallengeException exception = CloudflareChallengeException.class
                    .getConstructor(String.class, Map.class, String.class, IResponse.class, CookieJar.class)
                    .newInstance(message, context, challengeUrl, challengeResponse, currentCookieJar);

            notifyAllListeners(exception);
            throw exception;
        } catch (Exception e) {
            // 如果创建失败，则抛出JmcomicException作为回退
            throw new JmcomicException("Failed to create CloudflareChallengeException", context, e);
        }
    }

    /**
     * 通知所有监听器。
     *
     * @param e 抛出的异常
     */
    private static void notifyAllListeners(JmcomicException e) {
        if (JmModuleConfig.REGISTRY_EXCEPTION_LISTENER.isEmpty()) {
            return;
        }

        for (Map.Entry<Class<?>, Function<Exception, Void>> entry : JmModuleConfig.REGISTRY_EXCEPTION_LISTENER.entrySet()) {
            // 检查异常类型是否匹配
            if (entry.getKey().isInstance(e)) {
                try {
                    entry.getValue().apply(e); // 调用监听函数
                } catch (Exception listenerEx) {
                    JmModuleConfig.jmLog("exception.listener.error", "Exception listener " + entry.getValue() + " threw an exception: " + listenerEx.getMessage());
                }
            }
        }
    }

    // 私有构造函数，防止实例化
    private ExceptionUtils() {
    }
}